require 'logger'

class Scope
  attr_accessor :name
  
  @@next_id = 0
  @@next_register = 0
  @@next_if = 0
  @@next_while = 0
  @@stack = []
  @@runtime_functions = Hash.new

  @@log = Logger.new(STDOUT)
  @@log.progname = "Scope"
  @@log.datetime_format = ""
  @@log.level = Logger::INFO
  
  # ===Class Methods===
  def initialize()
    @log = Logger.new(STDOUT)
    @log.progname = "Scope #{@@next_id}"
    @log.datetime_format = ""
    @log.level = Logger::INFO

    # Leave position 0, 1, 2 for the stack frame header
    @next_offset = 2
    @name = @@next_id
    @@next_id = @@next_id + 1
    @variables = Hash.new
    @functions = Hash.new
    @parent = Scope.current
  end
  
  def self.current
    return @@stack.last
  end
  
  def self.push
    scope = Scope.new
    @@log.debug "Pushing a new scope with ID #{scope.name}"
    @@stack.push scope
  end
  
  def self.pop
    scope = @@stack.pop
    @@log.debug "Popping a scope with ID #{scope.name}"
    return scope
  end
  
  def self.next_register
    out = @@next_register
    @@next_register = @@next_register + 1
    return out
  end
  
  def self.next_if
    out = @@next_if
    @@next_if = @@next_if + 1
    return out
  end
  
  def self.next_while
    out = @@next_while
    @@next_while = @@next_while + 1
    return out
  end
  
  def self.insert_runtime(fn)
    name = fn.name
    # Rewrite the function name to reflect its type
    arg = fn.arguments[0]
    suffix = ""
    unless(arg.nil?)
      suffix = arg.var_type
    end
    if(name == "get")
      suffix = fn.return_var.var_type
    end
    fn.name = "#{name}_#{suffix}"
    @@runtime_functions["#{name}_#{suffix}"] = fn
  end
  
  def self.get_runtime(name, return_type, arg_type)
    suffix = arg_type
    if(name == "get")
      suffix = return_type
    end
    # puts ">>> Getting runtime: #{name}_#{suffix}"
    return @@runtime_functions["#{name}_#{suffix}"]
  end
  
  # ===Instance Methods===
  def next_offset(size=1)
    out = @next_offset
    @next_offset = @next_offset + size
    return out
  end
    
  def insert_variable(var)
    # Check for duplicates
    unless(@functions[var.name].nil?)
      raise "Illegal re-definition of variable '#{fn.name}'"
    end
    @variables[var.name] = var
  end
  
  def insert_function(fn)
    # Check for duplicates
    unless(@functions[fn.name].nil?)
      raise "Illegal re-definition of function '#{fn.name}'"
    end
    @functions[fn.name] = fn
  end
  
  def get_variable(name)
    var = @variables[name]
    if(var.nil?)
      # Dynamic Scoping
      reg = Scope.next_register
      if(@parent.nil?)
        var = nil
      else
        Generator.write_line("Reg[#{reg}] = MEM[Reg[FP]];", "Get the address of the parent frame")
        var = @parent.get_variable(name)
      end
      unless(var.nil?)
        var = var.clone
        var.addr_base = "Reg[#{reg}]"
      end
    end
    if(var.nil?)
      raise "Illegal reference to undeclared variable: '#{name}'"
    else
      @log.debug "Found variable '#{name}' with type: #{var.var_type}"
      return var
    end
  end
  
  def get_function(name, return_type, arguments)
    fn = @functions[name]
    if(fn.nil?)
      # Check for built-in runtime functions
      (arg, index_reg) = arguments[0]
      if(arg.nil?)
        type = nil
      else
        type = arg.var_type
      end
      fn = Scope.get_runtime(name, return_type, type)
    end
    if(fn.nil?)
      # Dynamic Scoping
      fn = @parent.get_function(name, return_type, arguments) unless(@parent.nil?)
    end
    if(fn.nil?)
      raise "Illegal reference to undeclared function: '#{name}'"
    end
    @log.debug "Found function '#{name}' with return type: #{fn.return_var.var_type}"
    return fn
  end
end